﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using RoMBot.MemoryLib;

namespace RoMBot.Classes
{
    public class EggPet
    {
        //private static bool? petInfoInstalled;
        private static readonly Dictionary<int, int> FoodLevels = new Dictionary<int, int> {
	        {204791, 5}, // CAKE
	        {204925, 10}, // CHEESE
	        {204924, 20}, // MILK
	        {204234, 30}, // BEEF
	        {204510, 1}, // Desert of Happiness
	        {204511, 10} // Favorite Meal
        };
        private static readonly Dictionary<int, int> FoodAmount = new Dictionary<int, int> {
	        {204791, 0}, // CAKE
	        {204925, 0}, // CHEESE
	        {204924, 0}, // MILK
	        {204234, 0}, // BEEF
	        {204510, 0}, // Desert of Happiness
	        {204511, 0} // Favorite Meal
        };
        public EggPet( int eggSlot )
        {
            this.EggSlot = eggSlot;
            this.Empty();
            if ( this.EggSlot > 0 )
            {
                this.Update();
            }
        }

        private void Empty()
        {
            this.Name = null;
            this.Available = false;
            this.EggId = 0;
            this.PetId = 0;
            this.Level = 0;
            this.Crafting = false;
            this.Summoned = false;
            this.Exp = 0;
            this.MaxExp = 0;
            this.TP = 0;
            this.MaxTP = 0;
            this.Loyalty = 0;
            this.Nourishment = 0;
            this.Aptitude = 0;
            this.Training = 0;
            this.Str = 0;
            this.Sta = 0;
            this.Dex = 0;
            this.Int = 0;
            this.Wis = 0;
            this.Mining = 0;
            this.Woodworking = 0;
            this.Herbalism = 0;
            this.Skills = new List<PetSkill>();
            this.Tool = new PetTool();
            this.Products = new List<PetProduct>();
        }

        public void Update()
        {
            var eggPetAddress = 0;
            if ( this.EggSlot > 0 )
            {
                eggPetAddress = Addresses.eggPetBaseAddress + 0x348 * ( this.EggSlot - 1 );
            }
            else
            {
                Bot.Warning( "Pet not updated. Bad pet slot passed" + this.EggSlot );
            }

            if ( this.EggSlot > 2 )
            {
                this.Available = Memory.ReadUInt( Addresses.rentEggSlotBase + ( this.EggSlot - 3 ) * 4 ) != 0xFFFFFFFF;
            }
            else
            {
                this.Available = true;
            }
            this.Address = eggPetAddress;

            this.EggId = Memory.ReadInt( eggPetAddress + Addresses.eggPetEggId_offset );

            if ( this.EggId > 0 && this.Available )
            {
                this.PetId = Memory.ReadInt( eggPetAddress + Addresses.eggPetPetId_offset );
                this.Name = Memory.ReadUtf8String( eggPetAddress );
                if ( string.IsNullOrEmpty( this.Name ) )
                    this.Name = MemoryTable.GetIdName( this.PetId );
                this.Level = Memory.ReadInt( eggPetAddress + Addresses.eggPetLevel_offset );
                this.Summoned = ( Memory.ReadInt( eggPetAddress + Addresses.eggPetSummoned_offset ) == 2 );
                this.Exp = Memory.ReadInt( eggPetAddress + Addresses.eggPetExp_offset );
                this.MaxExp = Memory.ReadIntPtr( Addresses.eggPetMaxExpTablePtr, 0x4 * this.Level );
                this.TP = Memory.ReadInt( eggPetAddress + Addresses.eggPetTP_offset );
                this.MaxTP = Memory.ReadInt( eggPetAddress + Addresses.eggPetMaxTP_offset );
                this.Loyalty = Memory.ReadInt( eggPetAddress + Addresses.eggPetLoyalty_offset );
                this.Nourishment = Memory.ReadInt( eggPetAddress + Addresses.eggPetNourishment_offset );
                this.Aptitude = Memory.ReadFloat( eggPetAddress + Addresses.eggPetAptitude_offset );
                this.Training = Memory.ReadInt( eggPetAddress + Addresses.eggPetTraining_offset );
                this.Str = Memory.ReadFloat( eggPetAddress + Addresses.eggPetStr_offset );
                this.Sta = Memory.ReadFloat( eggPetAddress + Addresses.eggPetSta_offset );
                this.Dex = Memory.ReadFloat( eggPetAddress + Addresses.eggPetDex_offset );
                this.Int = Memory.ReadFloat( eggPetAddress + Addresses.eggPetInt_offset );
                this.Wis = Memory.ReadFloat( eggPetAddress + Addresses.eggPetWis_offset );
                Skills.Clear();

                var skillBase = eggPetAddress + Addresses.eggPetSkills_offset;
                for ( var i = 0; i < 7; i++ )
                {
                    var id = Memory.ReadInt( skillBase + 0x8 * i );
                    if ( id > 0 )
                    {
                        this.Skills.Add( new PetSkill { Id = id, Name = MemoryTable.GetIdName( id ), Level = Memory.ReadInt( skillBase + 0x8 * i + 0x4 ) + 1 } );
                    }
                }
                this.Mining = Memory.ReadFloat( eggPetAddress + Addresses.eggPetMining_offset );
                this.Woodworking = Memory.ReadFloat( eggPetAddress + Addresses.eggPetWoodworking_offset );
                this.Herbalism = Memory.ReadFloat( eggPetAddress + Addresses.eggPetHerbalism_offset );
                this.Tool = new PetTool();
                var toolId = Memory.ReadInt( eggPetAddress + Addresses.eggPetToolId_offset );
                if ( toolId > 0 )
                {
                    this.Tool.Id = toolId;
                    this.Tool.Name = MemoryTable.GetIdName( toolId );
                    this.Crafting = ( Memory.ReadInt( eggPetAddress + Addresses.eggPetCrafting_offset ) == 10 );
                }
                else
                {
                    this.Crafting = false;
                }
                this.Products.Clear();
                var productsBase = eggPetAddress + Addresses.eggPetProducts_offset;
                for ( var i = 0; i < 3; i++ )
                {
                    var id = Memory.ReadInt( productsBase + 0x44 * i );
                    if ( id > 0 )
                    {
                        this.Products.Add( new PetProduct { Id = id, Name = MemoryTable.GetIdName( id ), ItemCount = Memory.ReadInt( productsBase + 0x44 * i + 0x10 ) } );
                    }
                }
            }
            else
            {
                Empty();
            }
        }

        public void Summon()
        {
            this.Update();
            while ( this.EggId > 0 && Bot.Player.Level > this.Level - 5 && this.Summoned == false && Bot.Player.Alive && Bot.Player.HP > 0 )
            {
                Keyboard.ReleaseKey( Profile.HotKeys.MOVE_FORWARD.Key );
                Helpers.RoMScript( "SummonPet(" + this.EggSlot + ")" );
                do
                {
                    Thread.CurrentThread.Join( Helpers.GetPing() );
                    this.Update();
                } while ( !this.Summoned && !Bot.Player.Battling && Bot.Player.Casting );
                Thread.CurrentThread.Join( Helpers.GetPing() );
                if ( Bot.Player.Battling )
                {
                    if ( Bot.Player.FindEnemy( true, 0, Bot.Player.EvalTargetDefault ) != null )
                    {
                        Bot.Player.Fight();
                    }
                }
                this.Update();
            }
        }

        public void Return()
        {
            this.Update();
            if ( this.EggId <= 0 || !this.Summoned )
            {
                return;
            }
            do
            {
                Helpers.RoMScript( "ReturnPet(" + this.EggSlot + ")" );
                Thread.CurrentThread.Join( 100 );
                this.Update();
            } while ( this.Summoned );
        }

        public static void CheckEggPets()
        {
            if ( Bot.Player == null )
            {
                Bot.Warning( "CheckEggPets called before player initialized." );
                return;
            }

            if ( Bot.Player.Battling )
            {
                Bot.Warning( "CheckEggPets will be called again when fight finishes." );
                return;
            }

            EggPet assistPet = null, craftPet = null;
            if ( Bot.Player.Profile.GetOptionValue<bool>( "EGGPET_ENABLE_ASSIST" ) )
            {
                if ( Bot.Player.Profile.GetOptionValue<int>( "EGGPET_ASSIST_SLOT" ) > 0 )
                {
                    assistPet = new EggPet( Bot.Player.Profile.GetOptionValue<int>( "EGGPET_ASSIST_SLOT" ) );
                }
                else
                {
                    Bot.Error( "Bad slot assigned for assist pet." );
                }
            }
            if ( Bot.Player.Profile.GetOptionValue<bool>( "EGGPET_ENABLE_CRAFT" ) )
            {
                if ( Bot.Player.Profile.GetOptionValue<int>( "EGGPET_CRAFT_SLOT" ) > 0 )
                {
                    craftPet = new EggPet( Bot.Player.Profile.GetOptionValue<int>( "EGGPET_CRAFT_SLOT" ) );
                }
                else
                {
                    Bot.Error( "Bad slot assigned for craft pet." );
                }
            }

            /*
            -- Checks they are not the same
	            if assistEgg and craftEgg and (assistEgg.EggSlot == craftEgg.EggSlot) then
		            -- if there are tools, craft, else assist

		            local choice = craftEgg:getBestTool()
		            -- Check if you have tools in inventory
		            if choice == nil and craftEgg.Tool.Id == nil then
			            -- No tools, disable craft
			            craftEgg = nil
		            else
			            -- Have tools, disable assist
			            assistEgg = nil
		            end

	            end
             * * */
            if ( assistPet != null )
            {
                CheckIfPetNeedsFood( assistPet );
                if ( !assistPet.Summoned )
                {
                    assistPet.Summon();
                }
            }
            if ( craftPet != null )
            {
                CheckIfPetNeedsFood( craftPet );
                if ( !craftPet.Crafting )
                {
                    craftPet.Craft();
                }
            }

            Bot.Player.AssistPet = assistPet;
            Bot.Player.CraftPet = craftPet;
        }

        public void Craft()
        {

        }

        internal static void UpdateFoods( bool updateInventory = true )
        {
            if ( updateInventory )
            {
                Bot.Player.Inventory.Update();
            }
            FoodAmount[ 204791 ] = Bot.Player.Inventory.GetItemCount( 204791, false );
            FoodAmount[ 204925 ] = Bot.Player.Inventory.GetItemCount( 204925, false );
            FoodAmount[ 204924 ] = Bot.Player.Inventory.GetItemCount( 204924, false );
            FoodAmount[ 204234 ] = Bot.Player.Inventory.GetItemCount( 204234, false );
            FoodAmount[ 204511 ] = Bot.Player.Inventory.GetItemCount( 204511, false );
            FoodAmount[ 204510 ] = Bot.Player.Inventory.GetItemCount( 204510, false );
            LastFoodUpdate = DateTime.Now;
        }

        private static DateTime LastFoodUpdate;
        internal static bool PlayerHasFood
        {
            get
            {
                UpdateFoods( ( DateTime.Now - LastFoodUpdate ).TotalMinutes > 10 );
                return FoodAmount.Values.Any( f => f > 0 );
            }
        }

        public static void CheckIfPetNeedsFood( EggPet pet )
        {
            if ( pet == null )
            {
                return;
            }
            UpdateFoods();

            // Get best nourishment food
            var foodId = 0;
            if ( pet.Nourishment <= 95 && FoodAmount[ 204791 ] > 0 )
            { // use cake
                foodId = 204791;
            }
            else if ( pet.Nourishment <= 90 && FoodAmount[ 204925 ] > 0 )
            { // use cheese
                foodId = 204925;
            }
            else if ( pet.Nourishment <= 80 && FoodAmount[ 204924 ] > 0 )
            { // use milk
                foodId = 204924;
            }
            else if ( pet.Nourishment <= 70 && FoodAmount[ 204234 ] > 0 )
            { // use beef
                foodId = 204234;
            }

            // Get best loyalty food
            var loyaltyFoodId = 0;
            if ( pet.Loyalty <= 90 && FoodAmount[ 204511 ] > 0 )
            { // Use Favorite Meal
                loyaltyFoodId = 204511;
            }
            else if ( pet.Loyalty <= 99 && FoodAmount[ 204510 ] > 0 )
            { // Use Dessert of Happiness
                loyaltyFoodId = 204510;
            }

            if ( ( FoodLevels.ContainsKey( foodId ) && pet.Nourishment <= ( 100 - FoodLevels[ foodId ] ) ) || // needs nourishment
           FoodLevels.ContainsKey( loyaltyFoodId ) && pet.Loyalty <= ( 100 - FoodLevels[ loyaltyFoodId ] ) ) // needs loyalty
            {
                if ( pet.Summoned )
                {
                    pet.Return();
                }

                // feed nourishment
                if ( foodId != 0 )
                {
                    while ( pet.Nourishment <= ( 100 - FoodLevels[ foodId ] ) && FoodAmount[ foodId ] > 0 )
                    {
                        pet.Feed( foodId );
                    }
                }

                // feed loyalty
                if ( loyaltyFoodId != 0 )
                {
                    while ( pet.Loyalty <= ( 100 - FoodLevels[ loyaltyFoodId ] ) && FoodAmount[ loyaltyFoodId ] > 0 )
                    {
                        pet.Feed( loyaltyFoodId );
                        Thread.CurrentThread.Join( 10 );
                    }
                }
            }
        }

        public void Feed( object foodNameOrId, object number = null )
        {
            this.Update();
            var originalFoodNumber = Bot.Player.Inventory.GetItemCount( foodNameOrId );
            var total = 1;
            var summonedState = this.Summoned;
            if ( number is string && number.ToString().ToLowerInvariant() == "all" )
            {
                total = originalFoodNumber;
            }
            else if ( number is int )
            {
                total = ( int ) number;
            }

            while ( total > ( originalFoodNumber - Bot.Player.Inventory.GetItemCount( foodNameOrId ) ) && Bot.Player.Inventory.GetItemCount( foodNameOrId, false ) > 0 )
            {
                var foodItem = Bot.Player.Inventory.FindItem( foodNameOrId );
                if ( foodItem == null )
                {
                    break;
                }

                Bot.Printf( "Feeding egg pet with: {0}", foodItem.Name );

                if ( Summoned )
                {
                    Return();
                    Helpers.Yrest( 200 );
                    Update();
                }

                // Insert food in pet feed slot
                Helpers.RoMScript( "ClearPetFeedItem()" );
                Helpers.Yrest( 200 );
                Helpers.RoMScript( "PickupBagItem(" + foodItem.BagId + ")" );
                Helpers.Yrest( 100 );
                Helpers.RoMScript( "ClickPetFeedItem()" );
                Helpers.Yrest( 100 );

                // How many left to do
                var numberDone = originalFoodNumber - Bot.Player.Inventory.GetItemCount( foodNameOrId );
                var numberLeft = total - numberDone;
                // How many times to feed this loop
                var times = foodItem.ItemCount >= numberLeft ? numberLeft : foodItem.ItemCount;

                for ( var i = 1; i <= times; i++ )
                {
                    //if()
                    Helpers.RoMScript( "FeedPet(" + this.EggSlot + ")" );
                    Helpers.Yrest( 500 );
                    foodItem.Update();
                    if ( foodItem.ItemCount <= 0 )
                    {
                        break;
                    }
                }

                Helpers.RoMScript( "ClearPetFeedItem()" );
                Helpers.Yrest( 200 );
            }

            Helpers.Yrest( 100 );
            this.Update();
            if ( summonedState && !this.Summoned )
            {
                this.Summon();
            }

            Helpers.Yrest( 200 );
            this.Update();
        }

        public List<PetProduct> Products { get; set; }
        public PetTool Tool { get; set; }
        public float Herbalism { get; set; }
        public float Woodworking { get; set; }
        public float Mining { get; set; }
        public List<PetSkill> Skills { get; set; }
        public float Wis { get; set; }
        public float Int { get; set; }
        public float Dex { get; set; }
        public float Sta { get; set; }
        public float Str { get; set; }
        public int Training { get; set; }
        public float Aptitude { get; set; }
        public int Nourishment { get; set; }
        public int Loyalty { get; set; }
        public int MaxTP { get; set; }
        public int TP { get; set; }
        public int MaxExp { get; set; }
        public int Exp { get; set; }
        public bool Summoned { get; set; }
        public bool Crafting { get; set; }
        public int Level { get; set; }
        public int PetId { get; set; }
        public int EggId { get; set; }
        public bool Available { get; set; }
        public string Name { get; set; }
        public int EggSlot { get; set; }
        public int Address { get; set; }
    }

    public class PetBaseItem
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class PetSkill : PetBaseItem
    {
        public int Level { get; set; }
    }

    public class PetTool : PetBaseItem
    {
    }

    public class PetProduct : PetBaseItem
    {
        public int ItemCount { get; set; }
    }
}
